/*
 * Copyright 2025 KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */

#include "inputmethodlistmodel.h"

#include <QDebug>
#include <QList>

InputMethodListModel::InputMethodListModel(const FcitxDbusProvider *provider, QObject *parent)
    : QObject(parent), provider_(provider)
{
  init();
}

void InputMethodListModel::init()
{
  if (provider_ == nullptr)
  {
    return;
  }

  if (provider_->dbusProxy() == nullptr)
  {
    return;
  }

  availableInputMethods();
  inputMethodInfoFromCurrentGroup();
}

void InputMethodListModel::availableInputMethods()
{
  auto imcall = provider_->dbusProxy()->AvailableInputMethods();
  auto imcallwatcher = new QDBusPendingCallWatcher(imcall, this);
  QObject::connect(imcallwatcher, &QDBusPendingCallWatcher::finished, this,
                   [this](QDBusPendingCallWatcher *watcher)
                   {
                     QDBusPendingReply<FcitxQtInputMethodEntryList> ims = *watcher;
                     if (!ims.isError())
                     {
                       availableInputMethods_ = ims.value();
                     }

                     watcher->deleteLater();
                   });
}

void InputMethodListModel::inputMethodInfoFromCurrentGroup()
{
  auto imcall0 = provider_->dbusProxy()->CurrentInputMethodGroup();
  auto imcallwatcher0 = new QDBusPendingCallWatcher(imcall0, this);
  connect(imcallwatcher0, &QDBusPendingCallWatcher::finished, this,
          [this](QDBusPendingCallWatcher *watcher)
          {
            QDBusPendingReply<QString> group = *watcher;
            if (!group.isError())
            {
              currentGroup_ = group.value();
            }

            watcher->deleteLater();
          });

  auto imcall1 =
      provider_->dbusProxy()->FullInputMethodGroupInfo(currentGroup_);
  auto imcallwatcher1 = new QDBusPendingCallWatcher(imcall1, this);
  connect(imcallwatcher1, &QDBusPendingCallWatcher::finished, this,
          [this](QDBusPendingCallWatcher *watcher)
          {
            QDBusPendingReply<QString, QString, QString, QVariantMap,
                              FcitxQtFullInputMethodEntryList>
                allInfo = *watcher;
            if (!allInfo.isError())
            {
              currentGroupInputMethods_ = allInfo.argumentAt<4>();

              emit currentGroupInputMethodsChanged();
            }

            watcher->deleteLater();
          });
}

void InputMethodListModel::addInputMethod(int index)
{
  if (index < 0 || index >= availableInputMethods_.size())
  {
    qDebug() << "Invalid index for add input method:" << index;
    return;
  }

  FcitxQtInputMethodEntry entry = availableInputMethods_.at(index);

  FcitxQtFullInputMethodEntry fullEntry;
  fullEntry.setUniqueName(entry.uniqueName());
  fullEntry.setName(entry.name());
  fullEntry.setNativeName(entry.nativeName());
  fullEntry.setIcon(entry.icon());
  fullEntry.setLabel(entry.label());
  fullEntry.setLanguageCode(entry.languageCode());
  fullEntry.setConfigurable(entry.configurable());
  fullEntry.setLayout("");
  fullEntry.setProperties(QVariantMap());
  currentGroupInputMethods_.append(fullEntry);

  availableInputMethods_.removeAt(index);
}

void InputMethodListModel::deleteInputMethod(int index)
{
  if (index < 0 || index >= currentGroupInputMethods_.size())
  {
    qDebug() << "Invalid index for deleting input method:" << index;
    return;
  }

  currentGroupInputMethods_.removeAt(index);
}

void InputMethodListModel::moveUpInputMethod(int index)
{
  if (index < 1 || index >= currentGroupInputMethods_.size())
  {
    qDebug() << "Invalid index for moving up input method:" << index;
    return;
  }

#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
  currentGroupInputMethods_.swapItemsAt(index, index - 1);
#else
  currentGroupInputMethods_.swap(index, index - 1);
#endif
}

void InputMethodListModel::moveDownInputMethod(int index)
{
  if (index < 0 || index >= currentGroupInputMethods_.size() - 1)
  {
    qDebug() << "Invalid index for moving down input method:" << index;
    return;
  }

#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
  currentGroupInputMethods_.swapItemsAt(index, index + 1);
#else
  currentGroupInputMethods_.swap(index, index + 1);
#endif
}

void InputMethodListModel::moveTo(int srcIndex, int destIndex)
{
  if (srcIndex < 0 || srcIndex >= currentGroupInputMethods_.size())
  {
    qDebug() << "Invalid source index for moving input method:" << srcIndex;
    return;
  }
  if (destIndex < 0 || destIndex >= currentGroupInputMethods_.size())
  {
    qDebug() << "Invalid destination index for moving input method:"
             << destIndex;
    return;
  }

  if (srcIndex == destIndex)
  {
    qDebug() << "Source and destination indices are the same, no move needed.";
    return;
  }

  FcitxQtFullInputMethodEntry temp = currentGroupInputMethods_.takeAt(srcIndex);

  currentGroupInputMethods_.insert(destIndex, temp);
}

void InputMethodListModel::updateCurrentGroupInputMethodList()
{
  if (provider_ == nullptr)
  {
    return;
  }

  if (provider_->dbusProxy() == nullptr)
  {
    return;
  }

  provider_->dbusProxy()->SetInputMethodGroupInfo(
      currentGroup_, "", convertToKeyValueList(currentGroupInputMethods_));

  emit currentGroupInputMethodsChanged();
}

const FcitxQtStringKeyValueList InputMethodListModel::convertToKeyValueList(
    FcitxQtFullInputMethodEntryList inputMethodList) const
{
  FcitxQtStringKeyValueList keyValueList;

  for (const auto &entry : inputMethodList)
  {
    FcitxQtStringKeyValue value;
    value.setKey(entry.uniqueName());
    value.setValue("");
    keyValueList.append(value);
  }

  return keyValueList;
}
